package middleware

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"html/template"
	"io/ioutil"
	"net/http"
	"reflect"

	"gitee.com/shoyer/framework/bizerror"
	"gitee.com/shoyer/framework/bizerror/code"
	"gitee.com/shoyer/framework/ctx"
	"gitee.com/shoyer/framework/ctx/request"
	"gitee.com/shoyer/framework/ctx/response"
	"gitee.com/shoyer/framework/enums/RenderType"
	"gitee.com/shoyer/framework/runtime"
	"gitee.com/shoyer/logger"
	"gitee.com/shoyer/service/config"
	"gitee.com/shoyer/util"
	"github.com/golang/protobuf/proto"
)

var (
	router *map[string]interface{}
)

type Middleware func(next http.Handler) http.Handler

func InitMapIfNeeded() {
	if router == nil {
		r := make(map[string]interface{})
		router = &r
	}
}

func Init(m *map[string]interface{}, mds ...Middleware) {
	InitMapIfNeeded()

	for k, v := range *m {
		if _, ok := (*router)[k]; ok {
			logger.Fatalf("router repeated: %s", k)
		}

		(*router)[k] = v
		http.Handle(k, Chain(mds...))
	}
}

func InitWithSession(m *map[string]interface{}, mds ...Middleware) {
	InitMapIfNeeded()

	ms := []Middleware{Sessio()}

	if len(mds) > 0 {
		ms = append(ms, mds...)
	}

	for k, v := range *m {
		if _, ok := (*router)[k]; ok {
			logger.Fatalf("router repeated: %s", k)
		}

		(*router)[k] = v
		http.Handle(k, Chain(ms...))
	}
}

func Chain(mds ...Middleware) http.Handler {
	// ! 注意这里执行顺序是从下到上
	var f http.Handler = http.HandlerFunc(Call)

	for i := len(mds) - 1; i >= 0; i-- {
		f = mds[i](f)
	}

	f = BuildCtx()(f)
	f = Logging()(f)
	// f = CreateCtx()(f) 在日志中创建

	return f
}

func Call(w http.ResponseWriter, r *http.Request) {
	path := r.URL.Path
	if handler, ok := (*router)[path]; ok {
		hTye := reflect.TypeOf(handler)
		in1 := reflect.New(hTye.In(1).Elem())
		if k, ok := in1.Interface().(proto.Message); ok {
			ReadBody(r, k)
			DumpProto(k)
		} else if _, ok := in1.Interface().(*request.DefaultRequest); ok {
			logger.Debugf("request: {}") // 默认请求不会有任何参数，这里只是为了统一日志
		} else {
			logger.Fatalf("not support request type. can not parse input params in body, pls check.")
		}

		contx := GetCtx(r)
		hVal := reflect.ValueOf(handler)

		respon := hVal.Call([]reflect.Value{reflect.ValueOf(contx), in1})
		anyError := respon[1].Interface()
		if util.IsNotNil(anyError) {
			RenderError(contx, w, anyError)
			return
		}

		RenderSuccess(contx, w, respon[0].Interface())
		return
	}

	w.Write([]byte(fmt.Sprintf("path: %s not found handler, pls check.", path)))
}

func RenderSuccess(ctx *ctx.Ctx, w http.ResponseWriter, result interface{}) {
	switch ctx.GetRenderType() {
	case RenderType.Proto:
		{
			var res proto.Message
			if util.IsNotNil(result) {
				res = result.(proto.Message)
			}
			ResponseProtoSuccess(ctx, w, res)
		}
	case RenderType.Json:
		{
			ResponseJsonSuccess(ctx, w, result)
		}
	case RenderType.Text:
		{
			var res response.Bytes
			if util.IsNotNil(result) {
				resB, isByte := result.([]byte)
				if !isByte {
					resP, isProto := result.(proto.Message)
					if isProto {
						ResponseProtoSuccess(ctx, w, resP)
						return
						// j, _ := json.Marshal(resP)
						// res = j
					}
				} else {
					res = resB
				}
			}
			ResponseTextSuccess(ctx, w, res)
		}
	case RenderType.Template:
		{
			var res *response.Template
			if util.IsNotNil(result) {
				res = result.(*response.Template)
			}

			ResponseTplSuccess(ctx, w, res)
		}
	}
}

func RenderError(ctx *ctx.Ctx, w http.ResponseWriter, anyError interface{}) {
	if util.IsNil(anyError) {
		return
	}

	err, ok := anyError.(bizerror.BizError) // 先转为业务错误
	if !ok {
		// 降级到普通错误
		err2, ok2 := anyError.(error)
		if ok2 { // 如果转换成功，将普通错误包装成业务错误
			err = bizerror.NewBizErrorWithCode(code.Error, err2.Error())
		} else {
			err = bizerror.NewBizError("unknown error type", anyError)
		}
	}

	if util.IsNil(err) {
		return
	}

	SetServerStatus(w)

	switch ctx.GetRenderType() {
	case RenderType.Proto:
		ResponseProtoError(ctx, w, err)
	case RenderType.Json:
		ResponseJsonError(ctx, w, err)
	case RenderType.Text:
		ResponseTextError(ctx, w, err)
	case RenderType.Template:
		ResponseJsonError(ctx, w, err) // TODO: rend Template error
	}

	err.Print()
}

// ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

func ResponseTplSuccess(ctx *ctx.Ctx, w http.ResponseWriter, tpl *response.Template) {
	w.Header().Set("Content-Type", "text/html")

	t, err := template.ParseFiles(tpl.Tpl)
	if err != nil {
		logger.Warn(err)
		return
	}

	err = t.Execute(w, tpl.Data)
	logger.ErrorfIfNotNil(err)
	logger.Debugf("response(tpl): %s : [length = %d]", "-", -1)
}

func ResponseTextError(ctx *ctx.Ctx, w http.ResponseWriter, err bizerror.BizError) {
	w.Header().Set("Content-Type", "text/plain")
	es := fmt.Sprintf("code=%d, message=%s, data=%v", err.Code(), err.Error(), err.Data())
	w.Write([]byte(es))
	logger.Debugf("response(text): %s : [length = %d]", es, len(es))
}

func ResponseTextSuccess(ctx *ctx.Ctx, w http.ResponseWriter, res response.Bytes) {
	w.Header().Set("Content-Type", "text/plain")
	w.Write(res)
	logger.Debugf("response(text): %s : [length = %d]", res, len(res))
}

func ResponseJsonError(ctx *ctx.Ctx, w http.ResponseWriter, err bizerror.BizError) {
	w.Header().Set("Content-Type", "application/json")
	res := response.Response{}
	res.Code = err.Code()
	res.Message = err.Error()
	res.Data = err.Data()
	result, err2 := json.Marshal(res)

	logger.ErrorfIfNotNil(err2)

	d, err2 := getEncode(ctx, w, result)
	logger.ErrorfIfNotNil(err2)

	w.Write(d)
	logger.Debugf("response(json): %s : [json length = %d] encoded length = %d", result, len(result), len(d))
}

func ResponseJsonSuccess(ctx *ctx.Ctx, w http.ResponseWriter, data interface{}) {
	w.Header().Set("Content-Type", "application/json")
	res := response.Response{}
	res.Code = code.Ok
	res.Data = data
	result, err := json.Marshal(res)
	logger.ErrorfIfNotNil(err)

	d, err := getEncode(ctx, w, result)
	logger.ErrorfIfNotNil(err)

	w.Write(d)
	logger.Debugf("response(json): %s : [json length = %d] [encoded length = %d]", result, len(result), len(d))
}

func ResponseProtoSuccess(ctx *ctx.Ctx, w http.ResponseWriter, data interface{}) {
	if data == nil {
		ResponseProtoData(ctx, w, nil)
	} else {
		ResponseProtoData(ctx, w, data.(proto.Message))
	}
}

func ResponseProtoError(ctx *ctx.Ctx, w http.ResponseWriter, err bizerror.BizError) {
	if util.IsNotNil(err.Data()) {
		ResponseHeaderError(ctx, w, err.Code(), err.Error(), err.Data().(string))
	} else {
		ResponseHeaderError(ctx, w, err.Code(), err.Error())
	}
}

// ResponseHeaderError ResponseHeaderError
func ResponseHeaderError(ctx *ctx.Ctx, w http.ResponseWriter, code int, message string, data ...string) {
	w.Header().Set("c", util.IntToString(code))
	w.Header().Set("m", base64.StdEncoding.EncodeToString([]byte(message)))
	if len(data) > 0 {
		w.Header().Set("d", base64.StdEncoding.EncodeToString([]byte(data[0])))
	}

	logger.Debugf("response(proto): [code=%d] [message=%s] [data=%s]", code, message, data)
	// boot.SendErrorLog(util.IntToString(code), message)
}

// ResponseProtoData ResponseProtoData
func ResponseProtoData(ctx *ctx.Ctx, w http.ResponseWriter, pb proto.Message) {
	if pb == nil {
		w.Write(nil)
		logger.Debugf("response(proto): %s : [json length = %d], [proto length = %d]", "", 0, 0)
		return
	}

	var data, err = proto.Marshal(pb)
	if err != nil {
		logger.Error(err.Error())
	}

	var jsonData, _ = json.Marshal(pb)

	// 显示设置内容长度，否则内容过长时会用chunked方式传输数据 ，会导致c#解决出错。
	// refer: https://www.cnblogs.com/facingwaller/p/6572543.html
	// refer: https://blog.csdn.net/yuzehome/article/details/54095932
	// refer: https://blog.csdn.net/c364902709/article/details/80828376

	// 如果内容过长，后面的字节还是会被置为0，会导致客户解析出现Protocol message contained an invalid tag (zero)错误，所以这种方式不行。
	// w.Header().Set("Content-Length", util.IntToString(len(data)))

	w.Write(data)
	logger.Debugf("response(proto): %s : [json length = %d], [proto length = %d]", jsonData, len(jsonData), len(data))
}

func ReadBody(r *http.Request, pb proto.Message) {
	data, err := ioutil.ReadAll(r.Body)
	if err != nil {
		logger.Errorf(err.Error())
		return
	}

	err1 := proto.Unmarshal(data, pb) //try parse as proto input
	if err1 == nil {
		return
	}

	err2 := json.Unmarshal(data, &pb) // try parse as json input
	if err2 == nil {
		return
	}

	logger.Warnf("the input is neither proto nor json format, pls check.")
}

// DumpProto DumpProto
func DumpProto(pb proto.Message) {
	jsonData, _ := json.Marshal(pb)
	logger.Debugf("request: %s", jsonData)
}

// SetServerStatus SetServerStatus
func SetServerStatus(w http.ResponseWriter) {
	status := runtime.GetOnlineStatus()

	if status == 1 { // 客户端收到这个状态，告诉客户端当前自己的，让客户端自动切换到另外一个端口连接。
		w.Header().Set("p", config.GetPort())
	}
}
